//
// Created by Alex on 2021/9/16.
//

#include <iostream>
#include <fstream>
#include <string>
#include <vector>

#include "utils.h"

using namespace std;

struct lex_fsm {
private:
    enum FSM {START, IDENT, NUM, STRING, MREM};
    bool solved = false;
    lexical_result result;
    ifstream src_file;
    string line;
    int state = START, top = 0, line_number = 1, col{};
    char buf[4096]{}, c{};
public:
    explicit lex_fsm(char * src_filename);
    lexical_result get_lexical_parse();
private:
    void proc_start();
    void proc_ident();
    void proc_num();
    void proc_string();
    void proc_mrem();
    void proc_end_of_group();
};


lexical_result lexical_analysis(char * src_filename, bool print_to_scr) {
    lexical_result result = lex_fsm(src_filename).get_lexical_parse();
    if (print_to_scr) {
        printf("lexical_analysis:\n");
        for (const lexical_parse& parse : result.parses)
            printf("%s\n", parse.output.c_str());
    }
    return result;
}

lex_fsm::lex_fsm(char * src_filename):
src_file(src_filename) {
    if (!src_file) {
        cerr << "No such file." << endl;
        exit(-1);
    }
}

lexical_result lex_fsm::get_lexical_parse() {
    if (solved) return result;
    while (getline(src_file, line)) {
        line += " ";
        for (col = 0; col < line.length(); col++) {
            c = line[col];
            switch (state) {
                case START: proc_start(); break;
                case IDENT: proc_ident(); break;
                case NUM: proc_num(); break;
                case STRING: proc_string(); break;
                case MREM: proc_mrem(); break;
            }
        }
        line_number++;
    }
    src_file.close();
    solved = true;
    return result;
}

inline void lex_fsm::proc_start() {
    if (isspace(c)) { return; }
    if (isalpha(c) || c == '_') { state = IDENT; }
    else if ('0' <= c && c <= '9') { state = NUM; }
    else if (c == '\"') { state = STRING; }
    else {
        switch (c) {
            case '/':
                c = line[++col];
                switch (c) {
                    case '/': col = (int)line.size(); return;
                    case '*': state = MREM; return;
                    default:
                        result.parses.insert(result.parses.end(), lexical_parse(line_number, "/", "DIV"));
                        col--;
                        return;
                }
            case '&':
                c = line[++col];
                if (c == '&') result.parses.insert(result.parses.end(), lexical_parse(line_number, "&&", "AND"));
                else ; // todo : err
                return;
            case '|':
                c = line[++col];
                if (c == '|') result.parses.insert(result.parses.end(), lexical_parse(line_number, "||", "OR"));
                else ; // todo : err
                return;
            case '>':
                c = line[++col];
                if (c == '=') result.parses.insert(result.parses.end(), lexical_parse(line_number, ">=", "GEQ"));
                else {
                    result.parses.insert(result.parses.end(), lexical_parse(line_number, ">", "GRE"));
                    col--;
                }
                return;
            case '<':
                c = line[++col];
                if (c == '=') result.parses.insert(result.parses.end(), lexical_parse(line_number, "<=", "LEQ"));
                else {
                    result.parses.insert(result.parses.end(), lexical_parse(line_number, "<", "LSS"));
                    col--;
                }
                return;
            case '=':
                c = line[++col];
                if (c == '=') result.parses.insert(result.parses.end(), lexical_parse(line_number, "==", "EQL"));
                else {
                    result.parses.insert(result.parses.end(), lexical_parse(line_number, "=", "ASSIGN"));
                    col--;
                }
                return;
            case '!':
                c = line[++col];
                if (c == '=') result.parses.insert(result.parses.end(), lexical_parse(line_number, "!=", "NEQ"));
                else {
                    result.parses.insert(result.parses.end(), lexical_parse(line_number, "!", "NOT"));
                    col--;
                }
                return;
            case '+': result.parses.insert(result.parses.end(), lexical_parse(line_number, "+", "PLUS")); return;
            case '-': result.parses.insert(result.parses.end(), lexical_parse(line_number, "-", "MINU")); return;
            case '*': result.parses.insert(result.parses.end(), lexical_parse(line_number, "*", "MULT")); return;
            case '%': result.parses.insert(result.parses.end(), lexical_parse(line_number, "%", "MOD")); return;
            case ';': result.parses.insert(result.parses.end(), lexical_parse(line_number, ";", "SEMICN")); return;
            case ',': result.parses.insert(result.parses.end(), lexical_parse(line_number, ",", "COMMA")); return;
            case '(': result.parses.insert(result.parses.end(), lexical_parse(line_number, "(", "LPARENT")); return;
            case ')': result.parses.insert(result.parses.end(), lexical_parse(line_number, ")", "RPARENT")); return;
            case '[': result.parses.insert(result.parses.end(), lexical_parse(line_number, "[", "LBRACK")); return;
            case ']': result.parses.insert(result.parses.end(), lexical_parse(line_number, "]", "RBRACK")); return;
            case '{': result.parses.insert(result.parses.end(), lexical_parse(line_number, "{", "LBRACE")); return;
            case '}': result.parses.insert(result.parses.end(), lexical_parse(line_number, "}", "RBRACE")); return;
            default: ;  // todo: errors
        }
    }
    top = 1;
    buf[0] = c;
}

inline void lex_fsm::proc_ident() {
    if (isalpha(c) || '0' <= c && c <= '9' || c == '_') {
        buf[top++] = c;
    } else {
        buf[top] = 0;
        string savetext(buf);
        string type_code;
        if (preserved::is_preserved(savetext)) { type_code = preserved::to_token(savetext); }
        else { type_code = "IDENFR"; }
        lexical_parse tempident(line_number, savetext, type_code);
        result.parses.insert(result.parses.end(), tempident);
        proc_end_of_group();
    }
}

inline void lex_fsm::proc_num() {
    if ('0' <= c && c <= '9') {
        buf[top++] = c;
    } else {
        buf[top] = 0;
        lexical_parse tempnum(line_number, string(buf), string("INTCON"));
        result.parses.insert(result.parses.end(), tempnum);
        proc_end_of_group();
    }
}

inline void lex_fsm::proc_string() {
    if (c != '\"') {
        buf[top++] = c;
    } else {
        buf[top++] = c;
        buf[top] = 0;
        state = START;
        // DON'T proc_end_of_group
        lexical_parse tempnum(line_number, string(buf), string("STRCON"));
        result.parses.insert(result.parses.end(), tempnum);
    }
}

inline void lex_fsm::proc_mrem() {
    if (c == '*') {
        c = line[++col];
        if (c == '/') {
            state = START;
            top = 0;
        }
        else col--;
    }
}

inline void lex_fsm::proc_end_of_group() {
    top = 0;
    col--;
    state = START;
}
